﻿using System.Collections.Generic;
using SharpStockLib.Data;

namespace SharpBackTestLib.Indicators
{
    /// <summary>
    /// Represents the simple moving average (SMA) indicator.
    /// </summary>
    public class SimpleMovingAverage : MovingAverageBase
    {
        private double _previousSum;

        /// <summary>
        /// Default constructor. Will use EMovingAverageValueType.Close as the type of MA calculcation.
        /// </summary>
        /// <param name="length">The length of the moving average</param>
        public SimpleMovingAverage(int length) : this(length, EMovingAverageValueType.Close)
        {
        }

        /// <summary>
        /// Constructs a SimpleMovingAverage using the specified length. The calculations will then use the specified data type.
        /// </summary>
        /// <param name="length">The length of the MA.</param>
        /// <param name="type">How to compute the MA : using the data's close value, the high, etc...</param>
        public SimpleMovingAverage(int length, EMovingAverageValueType type)
        {
            _dataBuffer = new Queue<IData>(length);
            Type = type;
            Length = length;
            _previousSum = 0.0;
        }

        /// <summary>
        /// Returns the value of the MA. Will return null if there is not enough data to perform the computation.
        /// </summary>
        /// <returns>The value of the MA, null if it could not be computed because of insufficient data.</returns>
        public override double? GetValue()
        {
            if (_dataBuffer.Count == Length)
                return _value;

            return null;
        }

        public override void OnNewData(IData newData)
        {
            // The point is to store the sum and remove the dequeued elements then add the new data. This will perform much less operations and will result in a huge performance boost.
            // e.g. instead of performing 20 sums + 1 division for a 20-period SMA, we will make 1 substraction + 1 addition + 1 division.
            // The great thing is that this number of operations will always be the same (3, except for HLC3 (5) and OHLC4 (6)). Therefore, the larger the period, the bigger the performance boost.
            while (_dataBuffer.Count >= Length)
            {
                IData toRemove = _dataBuffer.Dequeue();

                switch (Type)
                {
                    case EMovingAverageValueType.Close:
                        _previousSum -= toRemove.Close;
                        break;
                    case EMovingAverageValueType.Open:
                        _previousSum -= toRemove.Open;
                        break;
                    case EMovingAverageValueType.High:
                        _previousSum -= toRemove.High;
                        break;
                    case EMovingAverageValueType.Low:
                        _previousSum -= toRemove.Low;
                        break;
                    case EMovingAverageValueType.HLC3:
                        _previousSum -= (toRemove.High + toRemove.Low + toRemove.Close) / 3;
                        break;
                    case EMovingAverageValueType.OHLC4:
                        _previousSum -= (toRemove.Open + toRemove.High + toRemove.Low + toRemove.Close) / 4;
                        break;
                    default:
                        _previousSum -= toRemove.Close;
                        break;
                }
            }


            _dataBuffer.Enqueue(newData);
            switch (Type)
            {
                case EMovingAverageValueType.Close:
                    _previousSum += newData.Close;
                    break;
                case EMovingAverageValueType.Open:
                    _previousSum += newData.Open;
                    break;
                case EMovingAverageValueType.High:
                    _previousSum += newData.High;
                    break;
                case EMovingAverageValueType.Low:
                    _previousSum += newData.Low;
                    break;
                case EMovingAverageValueType.HLC3:
                    _previousSum += (newData.High + newData.Low + newData.Close) / 3;
                    break;
                case EMovingAverageValueType.OHLC4:
                    _previousSum += (newData.Open + newData.High + newData.Low + newData.Close) / 4;
                    break;
                default:
                    _previousSum += newData.Close;
                    break;
            }

            if (_dataBuffer.Count == Length)
            {
                _value = _previousSum / Length;
            }
        }
    }
}
